Although it is technically possible to use the same
service code both connected and queued (with simple changes such as
configuring operations as one-way, or adding another contract for the
one-way operations), in reality it is unlikely that you will actually
use the same service both ways. The reasons are similar to the arguments
made in the context of asynchronous calls. Synchronous calls and asynchronous
calls addressing the same business scenario often have to use different
workflows, and these differences will necessitate changes to the service
code to adapt it for each case. The use of queued calls adds yet another
barrier for using the same service code (both connected and
disconnected): changes to the transactional semantics of the service.
Consider, for example, Figure 1, which depicts an
online store application that uses connected calls only.
The Store service uses three
well-factored helper services to process the order: Order, Shipment, and Billing. In the connected scenario, the
Store service calls the Order service to place the order. Only if the
Order service succeeds in processing
the order (that is, if the item is available in the inventory) does the
Store service call the Shipment service, and only if the Shipment service succeeds does the Store service access the Billing service to bill the customer. The
connected case involves exactly one transaction created by the client,
and all operations commit or abort as one atomic operation. Now, suppose
the Billing service also exposes a
queued endpoint for the use of the Store service, as shown in Figure 2.
The queued call to the Billing
service will be played to the service in a separate transaction from
that of the rest of the store, and it could commit or abort separately
from the transaction that groups Order and Shipment. This, in turn, could jeopardize the
system’s consistency, so you must include some logic in the Billing service to detect the failure of the
other service and to initiate some compensating logic in the event that
it fails to do its work. As a result, the Billing service will no longer be the same
service used in the connected case.
1. Requiring Queuing
Since not every service can be connected and queued, and since
some services may be designed for a particular option and only that
option, WCF lets you constrain a service’s communication pattern. The
DeliveryRequirements
attribute also lets you
insist on queued or connected delivery of messages to the
service:
public enum QueuedDeliveryRequirementsMode
{
Allowed,
Required,
NotAllowed
}
[AttributeUsage(AttributeTargets.Interface|AttributeTargets.Class,
AllowMultiple = true)]
public sealed class DeliveryRequirementsAttribute : Attribute,...
{
public QueuedDeliveryRequirementsMode QueuedDeliveryRequirements
{get;set;}
public bool RequireOrderedDelivery
{get;set;}
public Type TargetContract
{get;set;}
}
This attribute can be used to constrain a contract (and all its
supporting endpoints) or a particular service type. The default value
of the QueuedDeliveryRequirements
property is QueuedDeliveryRequirementsMode.Allowed, so
these definitions are equivalent:
[ServiceContract]
interface IMyContract
{...}
[ServiceContract]
[DeliveryRequirements]
interface IMyContract
{...}
[ServiceContract]
[DeliveryRequirements(QueuedDeliveryRequirements =
QueuedDeliveryRequirementsMode.Allowed)]
interface IMyContract
{...}
QueuedDeliveryRequirementsMode.Allowed
grants permission for using the contract or the service with either
connected or queued calls. QueuedDeliveryRequirementsMode.NotAllowed
explicitly disallows the use of the MSMQ binding, so all calls on
the endpoint must be connected calls. Use
this value when the contract or the service is
explicitly designed to be used in a connected fashion
only. QueuedDeliveryRequirementsMode.Required is the opposite: it mandates
the use of the MSMQ binding on the endpoint, and it should be used
when the contract or the service is designed from the ground up to be
queued.
Even though the DeliveryRequirements attribute offers the
RequireOrderedDelivery property , if QueuedDeliveryRequirementsMode.Required is
used, then RequireOrderedDelivery
must be false, because queued calls
inherently are unordered and messages may be played back in any
order.
When the DeliveryRequirements
attribute is applied on an interface, it affects all services that
expose endpoints with that contract:
[ServiceContract]
[DeliveryRequirements(QueuedDeliveryRequirements =
QueuedDeliveryRequirementsMode.Required)]
interface IMyQueuedContract
{...}
The client as well can apply the DeliveryRequirements
attribute on its copy of the service contract.
When the DeliveryRequirements
attribute is applied on a service class, it affects all endpoints of
that service:
[DeliveryRequirements(QueuedDeliveryRequirements =
QueuedDeliveryRequirementsMode.Required)]
class MyQueuedService : IMyQueuedContract,IMyOtherContract
{...}
When applied on a service class while using the TargetContract property, the attribute
affects all endpoints of the service that expose the specified
contract:
[DeliveryRequirements(TargetContract = typeof(IMyQueuedContract),
QueuedDeliveryRequirements =
QueuedDeliveryRequirementsMode.Required)]
class MyService : IMyQueuedContract,IMyOtherContract
{...}